home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 23 code / ProjectDrag 1.1b4 / Sources / ProjectDrag Sources / Comments.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-07-10  |  27.7 KB  |  1,057 lines  |  [TEXT/MPS ]

  1. /* Comments.c: Comment handling for ProjectDrag
  2.  *
  3.  * A set of applets for drag and drop source control by Tim Maroney.
  4.  * See develop, issue 23 for details.
  5.  *
  6.  * Built on DropShell by Leonard Rosenthol, Stephan Somogyi, and Marshall Clow,
  7.  * and using the MoreFiles utilities by Jim Luther.
  8.  *
  9.  * This software is free, but don't modify and redistribute it without
  10.  * changing the status window to indicate your name and your changes!
  11.  */
  12.  
  13.  
  14. #include <Types.h>
  15. #include <Resources.h>
  16. #include <Dialogs.h>
  17. #include <ToolUtils.h>
  18.  
  19. #include "PDUtilities.h"
  20. #include "PDDialogs.h"
  21. #include "Comments.h"
  22. #include "TasksAndErrors.h"
  23.  
  24.  
  25. #define kChangeCommentDialog 204
  26. #define kCommentItem 5
  27.  
  28.  
  29. typedef struct
  30. {
  31.     StringPtr extension;
  32.     StringPtr commentStart;
  33.     StringPtr lineStart;
  34.     StringPtr lineEnd;
  35.     StringPtr blankLine;
  36.     StringPtr commentEnd;
  37. } CommentFormat;
  38.  
  39.  
  40. /* XXX yeah, yeah, this table should be stored in a user-editable file -- maybe in 1.1 */
  41.  
  42. #define kNumCommentFormats 8
  43.  
  44. CommentFormat pCommentFormats[kNumCommentFormats] =
  45. {
  46.     { "\p.c", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  47.     { "\p.h", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  48.     { "\p.cp", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  49.     { "\p.cpp", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  50.     { "\p.r", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  51.     { "\p.p", "\p(*\n", "\p", "\p", "\p", "\p*)\n" },
  52.     { "\p.a", "\p", "\p*", "\p", "\p*", "\p*\n" },
  53.     { "\p", "\p#\n", "\p#", "\p", "\p#", "\p#\n" } /* this line must be last */
  54. };
  55.  
  56.  
  57. pascal Boolean TheFilterProc(DialogPtr theDialog,EventRecord *ev,short *itemHit)
  58. {
  59.     return StdFilterProc(theDialog, ev, itemHit);
  60. }
  61.  
  62.  
  63. Boolean GetChangeComment(Boolean in, ConstStr255Param fileName, Str255 comment)
  64. {
  65.     DialogPtr dialog;
  66.     Boolean done = false;
  67.     Boolean result = false;
  68.     OSErr err = noErr;
  69.     Str255 what;
  70.     Str255 s;
  71.     Rect r;
  72.     Handle h;
  73.     short type;
  74.     
  75.     GetIndString(s, kProjectDragStrings, kExplainChange);
  76.     if (in)
  77.         GetIndString(what, kProjectDragStrings, kWhatYouChangedIn);
  78.     else
  79.         GetIndString(what, kProjectDragStrings, kWhatYouWillDoWith);
  80.     err = PtrToHand(s + 1, &h, s[0]);
  81.     err = ReplaceString(h, "\p<1>", what);
  82.     err = ReplaceString(h, "\p<2>", fileName);
  83.     s[0] = GetHandleSize(h);
  84.     BlockMoveData(*h, s + 1, s[0]);
  85.     DisposeHandle(h);
  86.     ParamText(s, NULL, NULL, NULL);
  87.     
  88.     dialog = GetNewDialog(kChangeCommentDialog, NULL, (WindowPtr)-1);
  89.     if (dialog == NULL) return false;
  90.     GetDialogItem(dialog, kCommentItem, &type, &h, &r);
  91.     SetDialogItemText(h, comment);
  92.     SelectDialogItemText(dialog, kCommentItem, 0, 32767);
  93.     SetDialogDefaultItem(dialog, ok);
  94.     SetDialogCancelItem(dialog, cancel);
  95.     SetDialogTracksCursor(dialog, true);
  96.     ShowWindow(dialog);
  97.     while (!done)
  98.     {
  99.         short itemHit;
  100.         
  101.         ModalDialog(TheFilterProc, &itemHit);
  102.         switch (itemHit)
  103.         {
  104.         case ok:
  105.             GetDialogItem(dialog, kCommentItem, &type, &h, &r);
  106.             GetDialogItemText(h, s);
  107.             BlockMove(s, comment, s[0] + 1);
  108.             done = true;
  109.             result = true;
  110.             break;
  111.             
  112.         case cancel:
  113.             err = userCanceledErr;
  114.             done = true;
  115.             break;
  116.         }
  117.     }
  118.     DisposeDialog(dialog);
  119.     return result;
  120. }
  121.  
  122.  
  123. Boolean IsTextFile(FSSpec *file)
  124. {
  125.     FInfo info;
  126.     OSErr err;
  127.     
  128.     err = FSpGetFInfo(file, &info);
  129.     if (err != noErr) return false;
  130.     return info.fdType == 'TEXT';
  131. }
  132.  
  133.  
  134. CommentFormat *GetCommentFormat(FSSpec *file)
  135. {
  136.     Str15 extension;
  137.     long i, length;
  138.     
  139.     /* get the extension from the file name */
  140.     for (i = file->name[0]; i > 0 && file->name[i] != '.'; i--)
  141.         ;
  142.     length = (file->name[0] - i) + 1;
  143.     if (length > 5)
  144.         return pCommentFormats + (kNumCommentFormats - 1);
  145.     BlockMoveData(file->name + i, extension + 1, length);
  146.     extension[0] = length;
  147.     
  148.     /* loop over the formats */
  149.     for (i = 0; i < kNumCommentFormats; i++)
  150.     {
  151.         if (EqualString(extension, pCommentFormats[i].extension, false, true))
  152.             return pCommentFormats + i;
  153.     }
  154.     
  155.     /* return the default */
  156.     return pCommentFormats + (kNumCommentFormats - 1);
  157. }
  158.  
  159.  
  160. OSErr SubstituteComments(StringHandle header, CommentFormat *format)
  161. {
  162.     OSErr err;
  163.     err = ReplaceString(header, "\p<comment start>", format->commentStart);
  164.     if (err < noErr) return err;
  165.     err = ReplaceString(header, "\p<line start>", format->lineStart);
  166.     if (err < noErr) return err;
  167.     err = ReplaceString(header, "\p<line end>", format->lineEnd);
  168.     if (err < noErr) return err;
  169.     err = ReplaceString(header, "\p<blank line>", format->blankLine);
  170.     if (err < noErr) return err;
  171.     if (err > noErr) err = noErr;
  172.     return err;
  173. }
  174.  
  175.  
  176.  
  177. OSErr GetHeaderFromFile(Handle fileData, CommentFormat *format,
  178.                         StringHandle blankHeader, StringHandle *header)
  179. {
  180.     OSErr err;
  181.     Byte headerState, fileState;
  182.     StringPtr fileLine;
  183.     StringPtr headerLine;
  184.     Boolean lineDoesntMatch;
  185.     Boolean neverMatched;
  186.     Boolean lastLineMatchedCommentEnd;
  187.     long headerLineLength, headerLength, fileLineLength, fileLength;
  188.     
  189.     /* loop over lines to match each line of blank header,
  190.      * accumulating lines into the header string handle
  191.      */
  192.     *header = NULL;
  193.     headerState = HGetState(blankHeader);
  194.     HLock(blankHeader);
  195.     fileState = HGetState(fileData);
  196.     HLock(fileData);
  197.     fileLength = GetHandleSize(fileData);
  198.     headerLength = GetHandleSize(blankHeader);
  199.     headerLine = *blankHeader;
  200.     fileLine = *fileData;
  201.     headerLineLength = LineSize(headerLine, headerLength);
  202.     fileLineLength = LineSize(fileLine, fileLength);
  203.     lineDoesntMatch = true;
  204.     neverMatched = true;
  205.     lastLineMatchedCommentEnd = false;
  206.     do
  207.     {
  208.         /* check for end of comment -- it means failure when followed by a blank line */
  209.         if (lastLineMatchedCommentEnd && fileLineLength == 1 && *fileLine == '\n')
  210.         {
  211.             err = -1;
  212.             break;
  213.         }
  214.         
  215.         lastLineMatchedCommentEnd = MatchLine(fileLine, fileLineLength, format->commentEnd + 1, format->commentEnd[0]);
  216.  
  217.         if (MatchLineUntilChar(fileLine, fileLineLength, headerLine, headerLineLength, '<'))
  218.         {
  219.             lineDoesntMatch = neverMatched = false;
  220.             
  221.             /* start matching file to next header line */
  222.             headerLine += headerLineLength;
  223.             headerLength -= headerLineLength;
  224.             headerLineLength = LineSize(headerLine, headerLength);
  225.             
  226.             /* skip blank lines in header */
  227.             while (headerLength > 0
  228.                    && MatchLine(headerLine, headerLineLength, format->lineStart + 1,
  229.                                    format->lineStart[0]))
  230.             {
  231.                 headerLine += headerLineLength;
  232.                 headerLength -= headerLineLength;
  233.                 headerLineLength = LineSize(headerLine, headerLength);
  234.             }
  235.         }
  236.         else
  237.         {
  238.             lineDoesntMatch = true;
  239.             
  240.             /* it's OK if a line doesn't match as long as we have been matching
  241.              * up to this point, because extra lines of comment are OK.
  242.              */
  243.             if (neverMatched) break;
  244.         }
  245.         
  246.         /* accumulate line into header handle */
  247.         if (*header == NULL)
  248.         {
  249.             *header = TempNewHandle(0, &err);
  250.             if (err != noErr) break;
  251.         }
  252.         err = PtrAndHand(fileLine, *header, fileLineLength);
  253.         
  254.         /* advance the file data pointer */
  255.         fileLine += fileLineLength;
  256.         fileLength -= fileLineLength;
  257.         fileLineLength = LineSize(fileLine, fileLength);
  258.  
  259.     } while (err == noErr && headerLength > 0 && fileLength > 0);
  260.     
  261.     HSetState(fileData, fileState);
  262.     HSetState(blankHeader, headerState);
  263.     
  264.     if (lineDoesntMatch || err != noErr)
  265.     {
  266.         /* XXX put up a "header corrupt" warning when something matched */
  267.         if (*header != NULL)
  268.         {
  269.             DisposeHandle(*header);
  270.             *header = NULL;
  271.         }
  272.     }
  273.     
  274.     return (lineDoesntMatch && err == noErr) ? -1 : err;
  275. }
  276.  
  277.  
  278. OSErr PadToColumn(Handle h, long column)
  279. {
  280.     OSErr err;
  281.     long lineStart, i;
  282.     long sizeNow = GetHandleSize(h), newSize;
  283.     
  284.     /* track backwards looking for end of line */
  285.     for (lineStart = sizeNow; lineStart > 0 && (*h)[lineStart-1] != '\n'; lineStart--)
  286.         ;
  287.     
  288.     if (sizeNow - lineStart >= column)
  289.     {
  290.         /* already gone too far -- insert a single space */
  291.         SetHandleSize(h, sizeNow + 1);
  292.         err = MemError();
  293.         if (err != noErr) return err;
  294.         (*h)[sizeNow] = ' ';
  295.         return noErr;
  296.     }
  297.     
  298.     /* add spaces until we get to the column */
  299.     newSize = sizeNow + (column - (sizeNow - lineStart));
  300.     SetHandleSize(h, newSize);
  301.     err = MemError();
  302.     if (err != noErr) return err;
  303.     for (i = sizeNow; i < newSize; i++)
  304.         (*h)[i] = ' ';
  305.     return noErr;
  306. }
  307.  
  308.  
  309. Boolean IsChangeCommentStartLine(StringPtr headerLine, long length,
  310.                                  CommentFormat *format)
  311. {
  312.     StringPtr s = headerLine;
  313.     
  314.     /* match the start of line sequence, if not null */
  315.     if (format->lineStart != NULL && *format->lineStart != 0)
  316.     {
  317.         long len2 = format->lineStart[0];
  318.         StringPtr s2 = format->lineStart + 1;
  319.         while (len2-- > 0 && length-- > 0)
  320.         {
  321.             if (*s2++ != *s++)
  322.                 return false;
  323.         }
  324.     }
  325.     
  326.     /* skip any number of tabs and spaces */
  327.     while (*s == '\t' || *s == ' ') s++;
  328.  
  329.     /* found it if the first non-tab is an angle bracket */
  330.     return (*s == '<');
  331. }
  332.  
  333.  
  334. OSErr FindFirstChangeComment(StringHandle header, CommentFormat *format,
  335.                              long *start, long *end)
  336. {
  337.     StringPtr headerLine;
  338.     Byte state;
  339.     long headerLineLength;
  340.     long headerLength;
  341.     Boolean found = false;
  342.     Boolean lastLineMatchedCommentEnd;
  343.     
  344.     state = HGetState(header);
  345.     HLock(header);
  346.     headerLine = *header;
  347.     headerLength = GetHandleSize(header);
  348.     headerLineLength = LineSize(headerLine, headerLength);
  349.     lastLineMatchedCommentEnd = false;
  350.     
  351.     /* loop over lines to find first line starting with tabs and angle bracket */
  352.     do
  353.     {
  354.         /* check for end of comment */
  355.         if (lastLineMatchedCommentEnd && headerLineLength == 1 && *headerLine == '\n')
  356.         {
  357.             /* no change comment */
  358.             *start = *end = (headerLine - (*header)) - 1;
  359.             break;
  360.         }
  361.         
  362.         lastLineMatchedCommentEnd = MatchLine(headerLine, headerLineLength, format->commentEnd + 1, format->commentEnd[0]);
  363.         
  364.         found = IsChangeCommentStartLine(headerLine, headerLineLength, format);
  365.         
  366.         if (!found)
  367.         {
  368.             /* if not found, advance to next line */
  369.             headerLine += headerLineLength;
  370.             headerLength -= headerLineLength;
  371.             headerLineLength = LineSize(headerLine, headerLength);
  372.         }
  373.         else
  374.         {
  375.             /* if found, record the start of the comment and scan for the next or the end */
  376.             *start = headerLine - *header;
  377.             
  378.             headerLine += headerLineLength;
  379.             headerLength -= headerLineLength;
  380.             headerLineLength = LineSize(headerLine, headerLength);
  381.             lastLineMatchedCommentEnd = false;
  382.             do
  383.             {
  384.                 if (MatchLine(headerLine, headerLineLength, format->commentEnd + 1, format->commentEnd[0]))
  385.                     break; /* adding the first change comment */
  386.                 
  387.                 if (IsChangeCommentStartLine(headerLine, headerLineLength, format))
  388.                     break; /* found start of next change comment */
  389.                 
  390.                 /* try the next line */
  391.                 headerLine += headerLineLength;
  392.                 headerLength -= headerLineLength;
  393.                 headerLineLength = LineSize(headerLine, headerLength);
  394.             } while (headerLength > 0);
  395.             
  396.             *end = (headerLine - (*header)) - 1;
  397.         }
  398.     } while (headerLength > 0 && !found);
  399.     
  400.     HSetState(header, state);
  401.     
  402.     return noErr;
  403. }
  404.  
  405.  
  406. OSErr GetRevisionNumber(FSSpec *file, short *revision)
  407. {
  408.     CKIDHandle theCKID;
  409.     OSErr err;
  410.     
  411.     /* get the CKID */
  412.     err = ExtractCKID(file, &theCKID);
  413.     
  414.     if (err != noErr)
  415.     {
  416.         /* if no CKID, it's revision 0! */
  417.         *revision = 0;
  418.     }
  419.     else
  420.     {
  421.         /* otherwise it's the number in the CKID */
  422.         Str31 revisionString;
  423.         long longRevision;
  424.         StringPtr s = (*theCKID)->projectPath; /* careful -- not locked down */
  425.         s += s[0] + 2;        /* skip the project name and null character */
  426.         s += s[0] + 2;        /* skip the user name and null character */
  427.         BlockMoveData(s, revisionString, s[0] + 1); /* copy the revision number */
  428.         StringToNum(revisionString, &longRevision);
  429.         *revision = (short)longRevision;
  430.         DisposeHandle((Handle)theCKID);
  431.     }
  432.     return noErr;
  433. }
  434.  
  435.  
  436. OSErr BuildComment(Handle *commentHandle, CommentFormat *format, short revision,
  437.                     Boolean checkingOut, StringPtr comment, StringPtr nickname)
  438. {
  439.     OSErr err;
  440.     Str31 revisionString;
  441.     unsigned long sects;
  442.     Str31 dateString;
  443.     char c;
  444.     
  445.     /* Build the change comment, aligned to four-column format.
  446.      * The end of the revision number (">") falls on column twelve (12);
  447.      * the end of the date on column twenty-four (24);
  448.      * the start of the initials on column twenty-nine (29);
  449.      * the start of the comment on column thirty-seven (37).
  450.      */
  451.     err = PtrToHand(format->lineStart + 1, commentHandle, format->lineStart[0]);
  452.     if (err != noErr) goto ErrorExit;
  453.     
  454.     /* add the revision string */
  455.     NumToString(checkingOut ? revision : revision + 1, revisionString);
  456.     if (checkingOut) revisionString[++revisionString[0]] = '+';
  457.     err = PadToColumn(*commentHandle, 12 - (revisionString[0] + 2));
  458.     if (err != noErr) goto ErrorExit;
  459.     c = '<';
  460.     err = PtrAndHand(&c, *commentHandle, 1);
  461.     if (err != noErr) goto ErrorExit;
  462.     err = PtrAndHand(revisionString + 1, *commentHandle, revisionString[0]);
  463.     if (err != noErr) goto ErrorExit;
  464.     c = '>';
  465.     err = PtrAndHand(&c, *commentHandle, 1);
  466.     if (err != noErr) goto ErrorExit;
  467.     
  468.     /* add the date string */
  469.     GetDateTime(§s);
  470.     DateString(sects, shortDate, dateString, NULL);
  471.     err = PadToColumn(*commentHandle, 24 - dateString[0]);
  472.     if (err != noErr) goto ErrorExit;
  473.     err = PtrAndHand(dateString + 1, *commentHandle, dateString[0]);
  474.     if (err != noErr) goto ErrorExit;
  475.     
  476.     /* add the nickname */
  477.     err = PadToColumn(*commentHandle, 28);
  478.     if (err != noErr) goto ErrorExit;
  479.     err = PtrAndHand(nickname + 1, *commentHandle, nickname[0]);
  480.     if (err != noErr) goto ErrorExit;
  481.     
  482.     /* add the comment, splitting among multiple lines if necessary;
  483.      * max line length is 100, so the max size of a section is 100-37 = 63
  484.      */
  485.     err = PadToColumn(*commentHandle, 36);
  486.     if (err != noErr) goto ErrorExit;
  487.     if (comment[0] <= 63)
  488.     {
  489.         err = PtrAndHand(comment + 1, *commentHandle, comment[0]);
  490.         if (err != noErr) goto ErrorExit;
  491.     }
  492.     else
  493.     {
  494.         /* split that sucker */
  495.         StringPtr strStart = comment + 1;
  496.         long strLen = comment[0];
  497.         while (strLen > 0)
  498.         {
  499.             StringPtr strEnd;
  500.             if (strLen > 63)
  501.             {
  502.                 strEnd = strStart + 63;
  503.                 while (strEnd[0] != ' ' && strEnd[0] != '\t' && strEnd > strStart)
  504.                     strEnd--;
  505.                 if (strStart == strEnd)
  506.                 {
  507.                     /* there were no spaces -- split arbitrarily at 63... */
  508.                     strEnd = strStart + 63;
  509.                 }
  510.             }
  511.             else
  512.             {
  513.                 strEnd = strStart + strLen;
  514.             }
  515.             
  516.             /* add this chunk */
  517.             err = PtrAndHand(strStart, *commentHandle, strEnd - strStart);
  518.             if (err != noErr) goto ErrorExit;
  519.             
  520.             /* advance to next chunk */
  521.             strLen -= (strEnd - strStart);
  522.             strStart = strEnd;
  523.             while (strLen > 0 && (strStart[0] == ' ' || strStart[0] == '\t'))
  524.                 strStart++, strLen--;
  525.             
  526.             /* add the end of this line and the start of the next */
  527.             if (strLen > 0)
  528.             {
  529.                 err = PtrAndHand(format->lineEnd + 1, *commentHandle, format->lineEnd[0]);
  530.                 if (err != noErr) goto ErrorExit;
  531.                 c = '\n';
  532.                 err = PtrAndHand(&c, *commentHandle, 1);
  533.                 if (err != noErr) goto ErrorExit;
  534.                 err = PtrAndHand(format->lineStart + 1, *commentHandle, format->lineStart[0]);
  535.                 if (err != noErr) goto ErrorExit;
  536.                 err = PadToColumn(*commentHandle, 36);
  537.                 if (err != noErr) goto ErrorExit;
  538.             }
  539.         }
  540.     }
  541.     
  542.     /* add the end of line */
  543.     err = PtrAndHand(format->lineEnd + 1, *commentHandle, format->lineEnd[0]);
  544.     if (err != noErr) goto ErrorExit;
  545.     c = '\n';
  546.     err = PtrAndHand(&c, *commentHandle, 1);
  547.     if (err != noErr) goto ErrorExit;
  548.     return noErr;
  549.     
  550. ErrorExit:
  551.     if (*commentHandle != NULL)
  552.     {
  553.         DisposeHandle(*commentHandle);
  554.         *commentHandle = NULL;
  555.     }
  556.     return err;
  557. }
  558.  
  559.  
  560. OSErr GetFirstTimeHeader(StringHandle *header, FSSpec *file, CommentFormat *format,
  561.                           StringPtr userName, StringPtr nickname, StringPtr comment, Boolean checkingOut)
  562. {
  563.     StringHandle commentHandle = NULL;
  564.     short revision;
  565.     OSErr err = noErr;
  566.     char c;
  567.  
  568.     /* get the template from the string in our resource file */
  569.     *header = Get1Resource('Comm', 1);
  570.     if (*header == NULL)
  571.     {
  572.         err = resNotFound;
  573.         goto ErrorExit;
  574.     }
  575.     DetachResource(*header);
  576.     
  577.     /* substitute the comment characters */
  578.     err = SubstituteComments(*header, format);
  579.     if (err < noErr) goto ErrorExit;
  580.     
  581.     /* substitute the file name */
  582.     err = ReplaceString(*header, "\p<file name>", file->name);
  583.     if (err < noErr) goto ErrorExit;
  584.     
  585.     /* substitute the user name */
  586.     err = ReplaceString(*header, "\p<user name>", userName);
  587.     if (err < noErr) goto ErrorExit;
  588.     
  589.     /* XXX substitute the copyright */
  590.  
  591.     /* get the revision number from the CKID */
  592.     err = GetRevisionNumber(file, &revision);
  593.     if (err != noErr) goto ErrorExit;
  594.     
  595.     err = BuildComment(&commentHandle, format, revision, checkingOut, comment, nickname);
  596.     if (err != noErr) goto ErrorExit;
  597.     
  598.     /* append the change comment to the header */
  599.     err = HandAndHand(commentHandle, *header);
  600.     DisposeHandle(commentHandle);
  601.     commentHandle = NULL;
  602.     if (err != noErr) goto ErrorExit;
  603.     
  604.     /* append the comment end */
  605.     err = PtrAndHand(format->commentEnd + 1, *header, format->commentEnd[0]);
  606.     if (err != noErr) goto ErrorExit;
  607.     
  608.     /* add another blank line or two */
  609.     c = '\n';
  610.     err = PtrAndHand(&c, *header, 1);
  611.     if (err != noErr) goto ErrorExit;
  612.     err = PtrAndHand(&c, *header, 1);
  613.     if (err != noErr) goto ErrorExit;
  614.  
  615.     return noErr;
  616.     
  617. ErrorExit:
  618.     if (*header != NULL)
  619.     {
  620.         DisposeHandle(*header);
  621.         *header = NULL;
  622.     }
  623.     if (commentHandle != NULL)
  624.         DisposeHandle(commentHandle);
  625.     return err;
  626. }
  627.  
  628.  
  629. OSErr ExtractFileHeader(CommentFormat *format, Handle fileData,
  630.                           StringHandle *header, long *headerEnd)
  631. {
  632.     StringHandle blankHeader = NULL;
  633.     Boolean atEnd;
  634.     Boolean lastLineMatchedCommentEnd;
  635.     OSErr err;
  636.     StringPtr fileLine;
  637.     long fileLength, fileLineLength, headerLength;
  638.     Byte fileState;
  639.     
  640.     /* get blank header from string resource */
  641.     blankHeader = Get1Resource('Comm', 1);
  642.     if (blankHeader == NULL)
  643.     {
  644.         err = resNotFound;
  645.         goto ErrorExit;
  646.     }
  647.     DetachResource(blankHeader);
  648.     
  649.     /* substitute the comment characters */
  650.     err = SubstituteComments(blankHeader, format);
  651.     if (err != noErr) goto ErrorExit;
  652.     
  653.     /* get the header */
  654.     err = GetHeaderFromFile(fileData, format, blankHeader, header);
  655.     if (err != noErr) goto ErrorExit;
  656.     
  657.     /* matched it! loop over lines to the end of comment, adding each
  658.      * line to the handle
  659.      */
  660.     headerLength = GetHandleSize(*header);
  661.     fileState = HGetState(fileData);
  662.     HLock(fileData);
  663.     fileLength = GetHandleSize(fileData) - headerLength;
  664.     fileLine = (*fileData) + headerLength;
  665.     fileLineLength = LineSize(fileLine, fileLength);
  666.     atEnd = lastLineMatchedCommentEnd = false;
  667.     do
  668.     {
  669.         atEnd = lastLineMatchedCommentEnd && fileLineLength == 1 && *fileLine == '\n';
  670.         if (atEnd) break;
  671.         
  672.         lastLineMatchedCommentEnd = MatchLine(fileLine, fileLineLength, format->commentEnd + 1, format->commentEnd[0]);
  673.     
  674.         /* accumulate line into header */
  675.         err = PtrAndHand(fileLine, *header, fileLineLength);
  676.         if (err != noErr) goto ErrorExit;
  677.         
  678.         /* advance the file data pointer */
  679.         fileLine += fileLineLength;
  680.         fileLength -= fileLineLength;
  681.         fileLineLength = LineSize(fileLine, fileLength);
  682.     } while (fileLength > 0);
  683.     
  684.     HSetState(fileData, fileState);
  685.     
  686.     /* return the end-of-header offset */
  687.     *headerEnd = GetHandleSize(*header);
  688.     
  689.     DisposeHandle(blankHeader);
  690.     return noErr;
  691.  
  692. ErrorExit:
  693.     if (blankHeader != NULL)
  694.         DisposeHandle(blankHeader);
  695.     return err;
  696. }
  697.  
  698.  
  699. OSErr ExtractCurrentChangeComment(CommentFormat *format, StringHandle header, StringPtr comment)
  700. {
  701.     OSErr err = noErr;
  702.     StringHandle h = NULL;
  703.     long start, end;
  704.     Byte state;
  705.     StringPtr s;
  706.     long i;
  707.     long stripLength;
  708.     long length;
  709.     
  710.     /* get the location of the first change comment */
  711.     err = FindFirstChangeComment(header, format, &start, &end);
  712.     if (err != noErr) return err;
  713.     length = end - start;
  714.     
  715.     /* extract the change comment */
  716.     h = TempNewHandle(end - start, &err);
  717.     if (err != noErr) return err;
  718.     BlockMoveData((*header) + start, *h, length);
  719.     
  720.     /* strip the revision number, initials, and date */
  721.     state = HGetState(h);
  722.     HLock(h);
  723.     for (i = 0, s = *h; i < length && *s != '>'; i++, s++)
  724.         ; /* strip to end of revision number -- right angle bracket */
  725.     for (s++; i < length && (*s == '\t' || *s == ' '); i++, s++)
  726.         ; /* strip tabs and spaces */
  727.     for ( ; i < length && (*s != '\t') && (*s != ' '); i++, s++)
  728.         ; /* strip non-white-space (date) */
  729.     for ( ; i < length && (*s == '\t' || *s == ' '); i++, s++)
  730.         ; /* strip tabs and spaces */
  731.     for ( ; i < length && (*s != '\t') && (*s != ' '); i++, s++)
  732.         ; /* strip non-white-space (initials) */
  733.     for ( ; i < length && (*s == '\t' || *s == ' '); i++, s++)
  734.         ; /* strip tabs and spaces */
  735.     stripLength = (s - *h);
  736.     HSetState(h, state);
  737.     if (length > stripLength)
  738.     {
  739.         length -= stripLength;
  740.         BlockMoveData(*h + stripLength, *h, length);
  741.         SetHandleSize(h, length);
  742.     }
  743.     else
  744.     {
  745.         SetHandleSize(h, 0);
  746.         length = 0;
  747.     }
  748.     
  749.     /* strip start of line characters */
  750.     if (format->lineStart[0] > 0)
  751.     {
  752.         Str31 theLineStart;
  753.         theLineStart[0] = format->lineStart[0] + 1;
  754.         theLineStart[1] = '\n';
  755.         BlockMoveData(format->lineStart + 1, theLineStart + 2, format->lineStart[0]);
  756.         while (ReplaceString(h, theLineStart, "\p ") > 0)
  757.             ;
  758.     }
  759.     
  760.     /* compress white space */
  761.     ReplaceString(h, "\p\n", "\p ");
  762.     ReplaceString(h, "\p\t", "\p ");
  763.     while (ReplaceString(h, "\p  ", "\p ") > 0)
  764.         ;
  765.     length = GetHandleSize(h);
  766.     
  767.     /* copy to the comment string */
  768.     BlockMoveData(*h, comment + 1, length);
  769.     comment[0] = length;
  770.     DisposeHandle(h);
  771.     return noErr;
  772. }
  773.  
  774.  
  775. OSErr AddChangeComment(FSSpec *file, CommentFormat *format, StringHandle header,
  776.                         StringPtr nickname, StringPtr comment, Boolean checkingOut)
  777. {
  778.     OSErr err = noErr;
  779.     long start, end;
  780.     short revision;
  781.     StringHandle commentHandle = NULL;
  782.     Byte state;
  783.     long newCommentSize;
  784.     
  785.     /* get the location of the first change comment */
  786.     err = FindFirstChangeComment(header, format, &start, &end);
  787.     if (err != noErr) goto ErrorExit;
  788.     
  789.     /* get the revision number from the CKID */
  790.     err = GetRevisionNumber(file, &revision);
  791.     if (err != noErr) goto ErrorExit;
  792.     
  793.     err = BuildComment(&commentHandle, format, revision, checkingOut, comment,
  794.                         nickname);
  795.     if (err != noErr) goto ErrorExit;
  796.  
  797.     /* check the current comment -- is it for this revision with a plus sign?
  798.      * if so, delete it. this is only when checking in...
  799.      */
  800.     state = HGetState(header);
  801.     HLock(header);
  802.     HLock(commentHandle); /* permanent lock -- disposed a little later */
  803.     newCommentSize = GetHandleSize(commentHandle);
  804.     
  805.     if (!checkingOut)
  806.     {
  807.         /* check to see if the current comment is for this revision */
  808.         StringPtr s;
  809.         long i;
  810.         
  811.         for (i = 0, s = (*header) + start; i < end - start && *s != '<'; i++, s++)
  812.             ;
  813.         if (i < end - start && *s == '<')
  814.         {
  815.             Str31 numberString;
  816.             long theNumber;
  817.             
  818.             numberString[0] = 0;
  819.             for (s++, i++; i < end - start && *s >= '0' && *s <= '9'; i++, s++)
  820.                 numberString[++numberString[0]] = *s;
  821.             StringToNum(numberString, &theNumber);
  822.             if (theNumber == revision)
  823.             {
  824.                 /* delete the current comment */
  825.                 Munger(header, start, NULL, (end - start) + 1, *commentHandle, 0);
  826.             }
  827.         }
  828.     }
  829.     
  830.     /* now add the comment at offset "start" */
  831.     HUnlock(header); /* needs to be unlocked for Munger! */
  832.     Munger(header, start, NULL, 0, *commentHandle, newCommentSize);
  833.     
  834.     /* clean up and go away */
  835.     HSetState(header, state);
  836.     DisposeHandle(commentHandle);
  837.     return noErr;
  838.  
  839. ErrorExit:
  840.     if (commentHandle != NULL)
  841.         DisposeHandle(commentHandle);
  842.     return err;
  843. }
  844.  
  845.  
  846. OSErr AddFirstTimeHeader(FSSpec *file, StringPtr userName, StringPtr nickname, StringPtr comment)
  847. {
  848.     StringHandle header = NULL;
  849.     CommentFormat *format = NULL;
  850.     Handle fileData;
  851.     short refNum;
  852.     OSErr err;
  853.  
  854.     /* is it a text file? if not, nothing to do */
  855.     if (!IsTextFile(file))
  856.         return noErr;
  857.     
  858.     /* does the user confirm that a header should be added? */
  859.     switch (ResTextYesNoCancel(kProjectDragStrings, kAddHeader, file->name, NULL, NULL, NULL))
  860.     {
  861.     case kConfirmYes:
  862.         break;
  863.     case kConfirmNo:
  864.         return noErr;
  865.     case kConfirmCancel:
  866.         return userCanceledErr;
  867.     }
  868.     
  869.     TaskStart(kProjectDragStrings, kAddingChangeComment, file->name, NULL, NULL, NULL);
  870.     
  871.     /* find the comment information for this file type */
  872.     format = GetCommentFormat(file);
  873.     
  874.     /* get new header with first time change comment */
  875.     err = GetFirstTimeHeader(&header, file, format, userName, nickname, comment, false);
  876.     if (err != noErr) return RaiseErrorNumber(err);
  877.     
  878.     /* get the original file data */
  879.     err = GetFileData(file, &fileData, &refNum);
  880.     if (err != noErr) return RaiseErrorNumber(err);
  881.     
  882.     /* write the file with its header */
  883.     err = WriteFileWithHeader(refNum, fileData, 0, header);
  884.     FSClose(refNum);
  885.     DisposeHandle(header);
  886.     DisposeHandle(fileData);
  887.     if (err == noErr)
  888.         TaskDone();
  889.     else
  890.         RaiseErrorNumber(err);
  891.     return err;
  892. }
  893.  
  894.  
  895. OSErr AddCheckinComment(FSSpec *file, StringPtr userName, StringPtr nickname, StringPtr comment)
  896. {
  897.     StringHandle header = NULL;
  898.     CommentFormat *format = NULL;
  899.     long headerEnd = 0;
  900.     short refNum = -1;
  901.     Handle fileData = NULL;
  902.     OSErr err;
  903.     
  904.     /* is it a text file? if not, just get an internal comment and get out - no text munging */
  905.     if (!IsTextFile(file))
  906.     {
  907.         if (!GetChangeComment(true, file->name, comment))
  908.             return userCanceledErr;
  909.         else
  910.             return noErr;
  911.     }
  912.  
  913.     TaskStart(kProjectDragStrings, kAddingChangeComment, file->name, NULL, NULL, NULL);
  914.     
  915.     /* find the comment information for this file type */
  916.     format = GetCommentFormat(file);
  917.  
  918.     /* get the original file data */
  919.     err = GetFileData(file, &fileData, &refNum);
  920.     if (err != noErr) goto ErrorExit;
  921.     
  922.     /* does the file have a header comment? */
  923.     err = ExtractFileHeader(format, fileData, &header, &headerEnd);
  924.     if (err != noErr)
  925.     {
  926.         /* no header -- does the user confirm adding one? */
  927.         switch (ResTextYesNoCancel(kProjectDragStrings, kAddHeader, file->name, NULL, NULL, NULL))
  928.         {
  929.         case kConfirmYes:
  930.             break;
  931.         case kConfirmNo:
  932.             FSClose(refNum);
  933.             TaskDone();
  934.             return noErr;
  935.         case kConfirmCancel:
  936.             err = userCanceledErr;
  937.             goto ErrorExit;
  938.         }        
  939.         
  940.         /* user confirmed -- get comment from user */
  941.         if (!GetChangeComment(true, file->name, comment))
  942.         {
  943.             err = userCanceledErr;
  944.             goto ErrorExit;
  945.         }
  946.         
  947.         /* get new header with first time change comment */
  948.         err = GetFirstTimeHeader(&header, file, format, userName, nickname, comment, false);
  949.         if (err != noErr) goto ErrorExit;
  950.     }
  951.     else
  952.     {
  953.         /* extract the change comment from the header */
  954.         err = ExtractCurrentChangeComment(format, header, comment);
  955.         if (err != noErr) goto ErrorExit;
  956.         
  957.         /* get the change comment from the user */
  958.         if (!GetChangeComment(true, file->name, comment))
  959.         {
  960.             err = userCanceledErr;
  961.             goto ErrorExit;
  962.         }
  963.         
  964.         /* put the change comment into the header */
  965.         err = AddChangeComment(file, format, header, nickname, comment, false);
  966.         if (err != noErr) goto ErrorExit;
  967.     }
  968.     
  969.     /* write the file with its header */
  970.     err = WriteFileWithHeader(refNum, fileData, headerEnd, header);
  971.     if (err != noErr) goto ErrorExit;
  972.     FSClose(refNum);
  973.     DisposeHandle(header);
  974.     DisposeHandle(fileData);
  975.     TaskDone();
  976.     return noErr;
  977.  
  978. ErrorExit:
  979.     if (refNum != -1)
  980.         FSClose(refNum);
  981.     if (header != NULL)
  982.         DisposeHandle(header);
  983.     if (fileData != NULL)
  984.         DisposeHandle(fileData);
  985.     return RaiseErrorNumber(err);
  986. }
  987.  
  988.  
  989. OSErr AddCheckoutComment(FSSpec *file, StringPtr userName, StringPtr nickname, StringPtr comment)
  990. {
  991.     StringHandle header = NULL;
  992.     CommentFormat *format = NULL;
  993.     long headerEnd = 0;
  994.     short refNum = -1;
  995.     Handle fileData = NULL;
  996.     OSErr err;
  997.  
  998.     /* is it a text file? if not, nothing to do */
  999.     if (!IsTextFile(file)) return noErr;
  1000.  
  1001.     TaskStart(kProjectDragStrings, kAddingChangeComment, file->name, NULL, NULL, NULL);
  1002.     
  1003.     /* find the comment information for this file type */
  1004.     format = GetCommentFormat(file);
  1005.     
  1006.     /* get the original file data */
  1007.     err = GetFileData(file, &fileData, &refNum);
  1008.     if (err != noErr) goto ErrorExit;
  1009.     
  1010.     /* does the file have a header comment? */
  1011.     err = ExtractFileHeader(format, fileData, &header, &headerEnd);
  1012.     if (err != noErr)
  1013.     {
  1014.         /* no header -- does the user confirm adding one? */
  1015.         switch (ResTextYesNoCancel(kProjectDragStrings, kAddHeader, file->name, NULL, NULL, NULL))
  1016.         {
  1017.         case kConfirmYes:
  1018.             break;
  1019.         case kConfirmNo:
  1020.             FSClose(refNum);
  1021.             TaskDone();
  1022.             return noErr;
  1023.         case kConfirmCancel:
  1024.             err = userCanceledErr;
  1025.             goto ErrorExit;
  1026.         }
  1027.         
  1028.         /* get new header with first time change comment */
  1029.         err = GetFirstTimeHeader(&header, file, format, userName, nickname, comment, true);
  1030.         if (err != noErr) goto ErrorExit;
  1031.     }
  1032.     else
  1033.     {
  1034.         /* has header -- add change comment */
  1035.         err = AddChangeComment(file, format, header, nickname, comment, true);
  1036.         if (err != noErr) goto ErrorExit;
  1037.     }
  1038.     
  1039.     /* write the file with its header */
  1040.     err = WriteFileWithHeader(refNum, fileData, headerEnd, header);
  1041.     if (err != noErr) goto ErrorExit;
  1042.     FSClose(refNum);
  1043.     DisposeHandle(header);
  1044.     DisposeHandle(fileData);
  1045.     TaskDone();
  1046.     return noErr;
  1047.  
  1048. ErrorExit:
  1049.     if (refNum != -1)
  1050.         FSClose(refNum);
  1051.     if (header != NULL)
  1052.         DisposeHandle(header);
  1053.     if (fileData != NULL)
  1054.         DisposeHandle(fileData);
  1055.     return RaiseErrorNumber(err);
  1056. }
  1057.